package media

import (
	"gitee.com/sdumeeting/sfu-server/global"
	"github.com/pion/rtcp"
	"github.com/pion/rtp/codecs"
	"github.com/pion/webrtc/v2"
	"github.com/pion/webrtc/v2/pkg/media/samplebuilder"
	"io"
)

// WebRTC 的全称是 Web Real-Time Communication，网页即时通讯
// 它通过简单的 API 为浏览器和移动应用程序提供实时通信功能

// RTP 的全称是 Real-time Transport Protocol，实时传输协议
// 该协议详细说明了在互联网上传递音频和视频的标准数据包格式
// 在 rtp.Header 中定义了 RTP 的报头格式

// RTP 报文由两部分组成：报头和有效载荷
// 报头格式如下：
// V：版本号，2 bit，RTP 协议的版本号
// P：填充位：1 bit，若为 1，则在该报文的尾部填充一个或多个额外的 8 bit 组
// X：扩展标志，1 bit，若为 1，则在 RTP 报头后跟有一个扩展报头
// CC：CSRC 计数器，4 bit，指示 CSRC 标识符的个数
// M：标记位，1 bit，不同的有效载荷有不同的含义
// PT：有效载荷类型，7 bit，用于说明 RTP 报文中有效载荷的类型
// Sequence Number：序列号，16 bit，用于标识发送者所发送的 RTP 报文的序列号
//     每发送一个报文，序列号增 1
//     接收者通过序列号来检测报文丢失情况，重新排序报文，恢复数据
// 同步信源（SSRC）标识符：32 bit，用于标识同步信源
//     该标识符是随机选择的，参加同一视频会议的两个同步信源不能有相同的 SSRC
// 特约信源（CSRC）标识符：32 bit，可以有 0 ~ 15 个
//     每个 CSRC 标识了包含在该 RTP 报文有效载荷中的所有特约信源

// RTCP 的全称是 Real-time Control Protocol，实时传输控制协议
// 在 RTP 会话期间，每个会话参与者周期性地向所有其他参与者发送 RTCP 控制信息包

// WebRTCEngine WebRTC 引擎
type WebRTCEngine struct {
	// 定义了如何通过 PeerConnection 建立连接
	config webrtc.Configuration

	// 定义了 PeerConnection 支持的编解码器
	mediaEngine webrtc.MediaEngine

	// 绑定了 WebRTC 的全局函数
	api *webrtc.API
}

// NewWebRTCEngine 创建 WebRTC 引擎
func NewWebRTCEngine() *WebRTCEngine {
	config := webrtc.Configuration{
		// 在 WebRTC 中，参与视频通信的双方必须先交换 SDP 信息
		// SDP 的全称是 Session Description Protocol，会话描述协议
		// 它由一行或多行 UTF-8 文本组成
		// 它描述了多媒体连接内容，例如分辨率、格式编码、加密算法
		// 便于在数据传输时两端都能够理解彼此的数据
		SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback,

		// ICE 的全称是 Interactive Connectivity Establishment，互动式连接建立
		// 这是一种框架，使各种 NAT 穿透技术（如 STUN、TURN等）可以实现统一
		// 该技术可以让客户端成功地穿透远程用户与网络之间可能存在的各类防火墙
		ICEServers: []webrtc.ICEServer{
			{
				// STUN 的全称是 Simple Traversal of UDP Through NAT
				// 这项技术允许位于 NAT 后的客户端找出自己的公网 IP 地址
				// 以及查出自己位于哪种类型的 NAT 及 NAT 所绑定的 Internet 端口
				// 这些信息可用于将两个同时处于 NAT 路由器之后的主机之间建立 UDP 通信
				URLs: []string{"stun:stun.stunprotocol.org:3478"},
			},
		},
	}

	// 将 VP8 和 Opus 添加进媒体引擎中
	// VP8 是视频图像编解码器，也是 WebRTC 视频引擎默认的编解码器
	// Opus 可以处理各种音频应用，它可以从低比特率窄带语音扩展到高音质的声音
	// 这里使用的时钟频率参考的 webrtc.MediaEngine 的 RegisterDefaultCodecs 方法
	mediaEngine := webrtc.MediaEngine{}
	mediaEngine.RegisterCodec(webrtc.NewRTPVP8Codec(webrtc.DefaultPayloadTypeVP8, 90000))
	mediaEngine.RegisterCodec(webrtc.NewRTPOpusCodec(webrtc.DefaultPayloadTypeOpus, 48000))

	// 创建 API 对象，并将媒体引擎添加进去
	api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))

	return &WebRTCEngine{config, mediaEngine, api}
}

// CreateMediaStreamSender 创建媒体流发送器
func (engine WebRTCEngine) CreateMediaStreamSender(offer webrtc.SessionDescription, pc **webrtc.PeerConnection,
	addVideoTrack, addAudioTrack **webrtc.Track) (answer webrtc.SessionDescription, err error) {

	// 根据接收的 API 对象，使用提供的配置创建一个新的 PeerConnection
	*pc, err = engine.api.NewPeerConnection(engine.config)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	if *addVideoTrack != nil && *addAudioTrack != nil {
		// 将视频 track 添加到 PeerConnection
		_, err = (*pc).AddTrack(*addVideoTrack)
		if err != nil {
			return webrtc.SessionDescription{}, err
		}

		// 将音频 track 添加到 PeerConnection
		_, err = (*pc).AddTrack(*addAudioTrack)
		if err != nil {
			return webrtc.SessionDescription{}, err
		}

		// 设置远程 peer 的会话描述
		err = (*pc).SetRemoteDescription(offer)
		if err != nil {
			return webrtc.SessionDescription{}, err
		}
	}

	// 启动 Peer 连接并生成本地描述
	answer, err = (*pc).CreateAnswer(nil)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	// 设置本地 peer 的会话描述
	err = (*pc).SetLocalDescription(answer)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	return answer, nil
}

// CreateMediaStreamReceiver 创建媒体流接收器
func (engine WebRTCEngine) CreateMediaStreamReceiver(offer webrtc.SessionDescription,
	pc **webrtc.PeerConnection, videoTrack, audioTrack **webrtc.Track,
	stop chan int, pli chan int) (answer webrtc.SessionDescription, err error) {

	// 根据接收的 API 对象，使用提供的配置创建一个新的 PeerConnection
	*pc, err = engine.api.NewPeerConnection(engine.config)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	// 创建一个新的视频 RTC 收发器，并将它添加到收发器集合中
	_, err = (*pc).AddTransceiverFromKind(webrtc.RTPCodecTypeVideo)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	// 创建一个新的音频 RTC 收发器，并将它添加到收发器集合中
	_, err = (*pc).AddTransceiverFromKind(webrtc.RTPCodecTypeAudio)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	// 设置一个事件处理程序，当远程 track 从远程 peer 到达时调用
	(*pc).OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
		switch track.PayloadType() {
		case webrtc.DefaultPayloadTypeVP8:
			// 创建视频轨道
			*videoTrack, err = (*pc).NewTrack(track.PayloadType(), track.SSRC(), "video", track.Label())

			go func() {
				for {
					select {
					case <-pli:
						// 向连接的 peer 发送提供的 RTCP 数据包
						// 里面的 pli 数据包会通知编码器有不定量的编码视频数据丢失
						_ = (*pc).WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}})
					case <-stop:
						return
					}
				}
			}()

			// 创建一个 SampleBuilder，它会缓冲数据包，直到媒体帧完成
			// 第一个参数表示构建一个完整的 sample 所需的时间
			// 它以 RTP 数据包序列号来衡量，这个值越大，丢失的数据包更少，但延迟会更高
			// 第二个参数从 RTP 数据包中提取 sample
			// 在这里的VP8Packet 表示存储在 RTP 数据包有效负载中的 VP8 报头
			builder := samplebuilder.New(7*5, &codecs.VP8Packet{})

			for {
				select {
				case <-stop:
					return
				default:
					// 读取 RTP 数据包
					r, err := track.ReadRTP()
					if err != nil {
						if err == io.EOF {
							return
						}
						global.Logger.Error(err.Error())
					}

					// 将 RTP 数据包添加到 builder 的缓冲区
					builder.Push(r)

					// 不断扫描 builder 缓冲区中的有效 sample，直到有效 sample 为空
					for s := builder.Pop(); s != nil; s = builder.Pop() {
						// 将 sample 进行分包并写入到轨道
						if err = (*videoTrack).WriteSample(*s); err != nil && err != io.ErrClosedPipe {
							global.Logger.Error(err.Error())
						}
					}
				}
			}
		case webrtc.DefaultPayloadTypeOpus:
			// 创建音频轨道
			*audioTrack, err = (*pc).NewTrack(track.PayloadType(), track.SSRC(), "audio", track.Label())

			rtpBuf := make([]byte, 1400)
			for {
				select {
				case <-stop:
					return
				default:
					i, err := track.Read(rtpBuf)
					if err == nil {
						_, _ = (*audioTrack).Write(rtpBuf[:i])
					} else {
						global.Logger.Error(err.Error())
					}
				}
			}
		default:
			global.Logger.Error("This codec is not supported by the system")
		}
	})

	// 设置远程 peer 的会话描述
	err = (*pc).SetRemoteDescription(offer)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	// 启动 peer 连接并生成本地描述
	answer, err = (*pc).CreateAnswer(nil)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	// 设置本地 peer 的会话描述
	err = (*pc).SetLocalDescription(answer)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	return answer, nil
}
